﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Wheels
{
    public static class HtmlUtility
    {
#pragma warning disable 1591

        #region html fragment

        private const string hfVersion = "Version:0.9";
        private const string hfpStartHTML = "StartHTML:";
        private const string hfpEndHTML = "EndHTML:";
        private const string hfpStartFragment = "StartFragment:";
        private const string hfpEndFragment = "EndFragment:";
        private const string hfStartFragment = "<html><body><!--StartFragment-->";
        private const string hfEndFragment = "<!--EndFragment--></body></html>";
        private const string fmtPos = "0000000000";
        private static readonly int lenPos = fmtPos.Length;
        private static readonly int lenNewLine = Environment.NewLine.Length;

        private static readonly int posStartHTML;
        private static readonly int posStartFragment;

        #endregion

        private static readonly Encoding UTF8 = Encoding.UTF8;

        static HtmlUtility()
        {
            #region initialize readonly fields

            posStartHTML =
                hfVersion.Length + lenNewLine +
                hfpStartHTML.Length + lenPos + lenNewLine +
                hfpEndHTML.Length + lenPos + lenNewLine +
                hfpStartFragment.Length + lenPos + lenNewLine +
                hfpEndFragment.Length + lenPos + lenNewLine;

            posStartFragment =
                posStartHTML + hfStartFragment.Length + lenNewLine;

            #endregion
        }

        public static string MakeHtmlFragment(this string source)
        {
            if (source == null) return null;

            var posEndFragment =
              posStartFragment + UTF8.GetByteCount(source);

            var posEndHTML =
              posEndFragment + lenNewLine + hfEndFragment.Length;

            var sb = new StringBuilder();
            sb.AppendLine(hfVersion);
            sb.Append(hfpStartHTML);
            sb.AppendLine(posStartHTML.ToString(fmtPos));
            sb.Append(hfpEndHTML);
            sb.AppendLine(posEndHTML.ToString(fmtPos));
            sb.Append(hfpStartFragment);
            sb.AppendLine(posStartFragment.ToString(fmtPos));
            sb.Append(hfpEndFragment);
            sb.AppendLine(posEndFragment.ToString(fmtPos));

            sb.AppendLine(hfStartFragment);
            sb.AppendLine(source);
            sb.Append(hfEndFragment);

            return sb.ToString();
        }

        public static string GetFragmentHtml(this string source)
        {
            if (source == null) return null;

            int? start = null;
            int? end = null;

            using (var sr = new StringReader(source))
            {
                while (sr.Peek() > -1)
                {
                    var line = sr.ReadLine();
                    if (line.BeginWith(hfpStartFragment, StringComparison.OrdinalIgnoreCase))
                    {
                        start = int.Parse(line.Substring(hfpStartFragment.Length), NumberStyles.Integer);
                    }
                    if (line.BeginWith(hfpEndFragment, StringComparison.OrdinalIgnoreCase))
                    {
                        end = int.Parse(line.Substring(hfpEndFragment.Length), NumberStyles.Integer);
                    }

                    if (start.HasValue && end.HasValue)
                        break;
                }
            }

            return UTF8.GetString(UTF8.GetBytes(source), start.Value, end.Value - start.Value);
        }



        public static string HtmlEncode(this string source)
        {
            if (source == null) return null;

            var sb = new StringBuilder();
            foreach (var c in source)
            {
                switch (c)
                {
                    case '&':
                        sb.Append("&amp;");
                        break;
                    case '<':
                        sb.Append("&lt;");
                        break;
                    case '>':
                        sb.Append("&gt;");
                        break;
                    case '"':
                        sb.Append("&quot;");
                        break;
                    default:
                        sb.Append(c);
                        break;
                }
            }
            return sb.ToString();
        }

        private static readonly Dictionary<string, char> smHtmlEntityMap =
            new Dictionary<string, char>
            {
                { "amp", '&' },
                { "lt", '<' },
                { "gt", '>' },
                { "quot", '"' },
                { "AMP", '&' },
                { "LT", '<' },
                { "GT", '>' },
                { "QUOT", '"' },
            };


        private static readonly Regex smHtmlEntityPattern = new Regex(@"&(#(\d+|x[0-9a-f]+)|[a-z]+);?\b", RegexOptions.IgnoreCase | RegexOptions.ECMAScript);
        public static string HtmlDecode(this string source)
        {
            return smHtmlEntityPattern.Replace(source, m =>
            {
                var code = m.Groups[2].Value;
                if (code.Length > 0)
                {
                    if (code[0] == 'x' || code[0] == 'X')
                    {
                        return ((char)int.Parse(code.Substring(1), NumberStyles.HexNumber)).ToString();
                    }
                    else
                    {
                        return ((char)int.Parse(code.Substring(1), NumberStyles.Integer)).ToString();
                    }
                }
                else
                {
                    char c;
                    if (smHtmlEntityMap.TryGetValue(m.Groups[1].Value, out c))
                        return c.ToString();
                }

                return m.Value;
            });
        }
    }
}
